﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data.Objects.DataClasses;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using Model;

namespace ModelInitializer
{
    public class Initializer
    {
        #region Lifecycle

        public Initializer() { }

        #endregion Lifecycle

        #region Properties

        #endregion Properties

        #region Methods

        /// <summary>
        /// Deletes the database, then parses the "FxCop rules to ISO 9126 
        /// subcharacteristics mapping" into it.
        /// </summary>
        /// <exception cref="ArgumentNullException">
        /// When any of the arguments are null
        /// </exception>
        public void Initialize(ActualEntities context, string iso, string mapping, string effectMatrix,
            Uri fxCopRulesPath, Uri visualStudioRulesPath)
        {
            if (context == null)
                throw new ArgumentNullException("context");
            if (iso == null)
                throw new ArgumentNullException("iso");
            if (mapping == null)
                throw new ArgumentNullException("mapping");
            if (effectMatrix == null)
                throw new ArgumentNullException("effectMatrix");
            if (fxCopRulesPath == null)
                throw new ArgumentNullException("rulesPath");

            Console.Write("Clearing DB... ");
            OnProgressChanged(-1, "Adatbázis törlése...");
            context.ClearDatabase();
            OnProgressChanged(0, "kész.");
            Console.WriteLine("done.");

            Console.Write("Parsing ISO characteristics to DB... ");
            ParseIsoCharacteristics(context, iso);
            Console.WriteLine("done.");

            Console.Write("Parsing FxCop rules to DB... ");
            ParseFxCopRules(context, fxCopRulesPath, visualStudioRulesPath);
            Console.WriteLine("done.");

            Console.Write("Parsing mappings to DB... ");
            ParseMapping(context, mapping);
            Console.WriteLine("done.");

            Console.Write("Parsing questionnaire to DB... ");
            ParseEffectMatrix(context, effectMatrix, false);
            Console.WriteLine("done.");
            Console.ReadLine();
        }

        public void RefreshEffectMatrix(ActualEntities context, string effectMatrix)
        {
            ParseEffectMatrix(context, effectMatrix, true);
        }

        private void ParseIsoCharacteristics(ActualEntities context, string iso)
        {
            OnProgressChanged(-1, "ISO 9126 minőségi karakterisztikák mentése az adatbázisba...");
            string[] allLines = iso.Split(new string[] { System.Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);

            var query = from line in allLines
                        where line.Equals("Characteristics;Subcharacteristics;Definitions") == false
                        let data = line.Split(';')
                        select new
                        {
                            Characteristics = data[0],
                            Subcharacteristics = data[1],
                            Definitions = (data[2] == null ? "" : data[2])
                        };
            int characteristicsCount = query.Count();
            int characteristicsDone = 0;
            foreach (var item in query)
            {
                QualityCharacteristic qc;
                if (context.QualityCharacteristics.Count() == 0
                    || context.QualityCharacteristics.Any(p => p.Name.Equals(item.Characteristics)) == false)
                {
                    qc = new QualityCharacteristic()
                    {
                        Name = item.Characteristics
                    };
                    context.QualityCharacteristics.AddObject(qc);
                    context.SaveChanges();
                }
                else
                    qc = context.QualityCharacteristics.First(p => p.Name.Equals(item.Characteristics));
                Category_Subcharacteristic sc =
                    new Category_Subcharacteristic()
                    {
                        Name = item.Subcharacteristics,
                        QualityCharacteristicName = item.Characteristics,
                        QualityCharacteristic = qc
                    };
                context.Category_Subcharacteristic.AddObjectSafe(sc);
                qc.Category_Subcharacteristic.Add(sc);
                context.SaveChanges();
                OnProgressChanged(++characteristicsDone, characteristicsCount);
            }
            Debug.Assert(context.QualityCharacteristics.Count() > 0);
            Debug.Assert(context.Categories.Count() > 0);
            Debug.Assert(context.Category_Subcharacteristic.Count() > 0);
            OnProgressChanged(100, "...kész.");
        }

        #region Parse Rules

        private void ParseFxCopRules(ActualEntities context, Uri fxCopRulesPath, Uri visualStudioRulesPath)
        {
            if (visualStudioRulesPath != null
                && Directory.Exists(visualStudioRulesPath.LocalPath) == true)
            {
                OnProgressChanged(-1, "Visual Studio 2010 Team Tools\\Static Analysis Tools\\FxCop szabályok betöltése az adatbázisba...");
                ParseFxCopRulesHelper(context, visualStudioRulesPath);
            }
            else
            {
                OnProgressChanged(-1, "FxCop 10.0 szabályok betöltése az adatbázisba...");
                ParseFxCopRulesHelper(context, fxCopRulesPath);
            }

            Debug.Assert(context.Rules.Count() > 0);
            Debug.Assert(context.Rule_FxCopRule.Count() > 0);
            OnProgressChanged(100, "...kész.");
        }

        private void ParseFxCopRulesHelper(ActualEntities context, Uri rulesPath)
        {
            string[] files = Directory.GetFiles(rulesPath.LocalPath);
            Console.WriteLine();
            int filesCount = files.Count();
            int filesDone = 0;
            foreach (var file in files)
            {
                Console.Write("   " + file.Split('\\').Last() + "... ");
                ParseRulesAssemblyResources(context, file);
                Console.WriteLine("kész.");
                OnProgressChanged(++filesDone, filesCount);
            }
        }

        private void ParseRulesAssemblyResources(ActualEntities context, string file)
        {
            if (file.EndsWith("DataflowRules.dll") == true)//FxCop can't use that
                return;
            if (file.EndsWith("Rules.dll") == false)
                throw new ArgumentException("Filename not ends with Rules.dll !", file);

            System.Reflection.Assembly rulesAssembly = System.Reflection.Assembly.LoadFrom(file);
            string[] splittedFile = file.Split(new string[] { "\\" }, StringSplitOptions.None);
            string category =
                splittedFile.Last().Substring(0, splittedFile.Last().Length - "Rules.dll".Length);
            using (Stream xmlStream = rulesAssembly.GetManifestResourceStream(
                "Microsoft.FxCop.Rules." + category + "." + category + "Rules.xml"))
            {
                XElement rules = XElement.Load(xmlStream);
                foreach (var rule in rules.Elements("Rule"))
                {
                    ParseRule(context, rule, rulesAssembly);
                }
            }
        }

        private void ParseRule(ActualEntities context, XElement rule, System.Reflection.Assembly rulesAssembly)
        {
            EntityCollection<FxCopResolution> resolutions = new EntityCollection<FxCopResolution>();
            foreach (var resolution in rule.Elements("Resolution"))
            {
                FxCopResolution fxCopResolution = new FxCopResolution();
                XAttribute name = resolution.Attribute("Name");
                fxCopResolution.Name = (name == null ? "" : name.Value);
                fxCopResolution.Content = resolution.Value;

                resolutions.Add(fxCopResolution);
            }

            string[] splittedFullName = rulesAssembly.FullName.Split(',');
            string fileName = splittedFullName.First() + ".dll";
            string assemblyVersion = splittedFullName[1].Split('=')[1];
            //object[] attributes = rulesAssembly.GetCustomAttributes(
            //    typeof(AssemblyVersionAttribute), false);
            //object versionAttribute = attributes.First();
            //string fileVersion = ((AssemblyVersionAttribute)(versionAttribute)).Version;

            Rule_FxCopRule fxCopRule =
                new Rule_FxCopRule()
                {
                    TypeName = rule.Attribute("TypeName").Value,
                    Category = rule.Attribute("Category").Value,
                    Id = rule.Attribute("CheckId").Value,
                    Name = rule.Element("Name").Value,
                    Description = rule.Element("Description").Value,
                    Url = rule.Element("Url").Value,
                    FxCopResolutions = resolutions,
                    Email = rule.Element("Email").Value,
                    MessageLevelCertainty = (int)rule.Element("MessageLevel").Attribute("Certainty"),
                    MessageLevelContent = rule.Element("MessageLevel").Value,
                    FixCategories = rule.Element("FixCategories").Value,
                    Owner = rule.Element("Owner").Value,
                    FileName = fileName,
                    FileVersion = assemblyVersion//fileVersion
                };

            foreach (var resolution in fxCopRule.FxCopResolutions)
            {
                resolution.FxCopRuleId = fxCopRule.Id;
                resolution.Rule_FxCopRule = fxCopRule;
                context.FxCopResolutions.AddObject(resolution);
            }
            context.Rule_FxCopRule.AddObjectSafe(fxCopRule);
            context.SaveChanges();
        }

        #endregion Parse Rules

        #region ParseMapping

        private void ParseMapping(ActualEntities context, string mapping)
        {
            OnProgressChanged(-1, "Leképezés elemeinek mentése az adatbázisba...");
            List<string> allLines =
                new List<string>(mapping.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries));
            List<string> header =
                new List<string>(allLines[0].TrimStart(';').Split(';'));
            allLines.RemoveAt(0);
            SortedSet<string> notFound = new SortedSet<string>();
            int linesCount = allLines.Count;
            int linesDone = 0;
            foreach (var line in allLines)
            {
                string[] allValues = line.Split(';');
                string categoryName = allValues.First();
                // If the current is a line of a characteristic, not a subcharacteristic
                // skip, else
                if (context.QualityCharacteristics.Any(p => p.Name.Equals(categoryName)) == false)
                {
                    for (int i = 0; i < allValues.Length - 1; i++)
                    {
                        if (allValues[i + 1].Equals("") == false && allValues[i + 1].Equals("0") == false)
                        {
                            string checkId = header[i];
                            var query = context.Rules.Where(p => p.Id.Equals(checkId));
                            Rule r = null;
                            if (query.Count() > 0)
                            {
                                r = query.First();
                            }
                            else if (notFound.Contains(checkId) == false)
                            {
                                notFound.Add(checkId);
                                r = new Rule() { Id = header[i] };
                            }
                            Category c = context.Categories.First(p => p.Name.Equals(categoryName));
                            context.Mappings.AddObject(
                                new Mapping()
                                {
                                    CategoryName = categoryName,
                                    RuleId = header[i],
                                    Category = c,
                                    Rule = r
                                });
                            context.SaveChanges();
                        }
                    }
                }
                OnProgressChanged(++linesDone, linesCount);
            }
            WriteWarnings(notFound);
            Debug.Assert(context.Mappings.Count() > 0);
            OnProgressChanged(100, "...kész.");
        }

        private void WriteWarnings(SortedSet<string> notFound)
        {
            Console.WriteLine();
            string warning = "";
            foreach (var item in notFound)
            {
                Console.Write(item + ", ");
                warning += item + ", ";
            }
            warning.TrimEnd(',', ' ');
            warning += " szabályok hiányoznak!";
            Console.WriteLine("not found in Rules!");
            OnProgressChanged(-1, warning);
        }

        #endregion Parse Mapping

        private void ParseEffectMatrix(ActualEntities context, string effectMatrix, bool refresh)
        {
            if (refresh == true)
            {
                DeleteEffectMatrix(context);
            }
            OnProgressChanged(-1, "Hatásmátrix elemeinek mentése az adatbázisba...");
            List<string> allLines =
                new List<string>(effectMatrix.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries));
            List<string> header =
                new List<string>(allLines[0].TrimStart(';').Split(';'));
            allLines.RemoveAt(0);
            //ParseHeader(header);

            string[][] allValues2 = new string[allLines.Count][];
            for (int i = 0; i < allValues2.Length; i++)
            {
                allValues2[i] = allLines[i].Split(';');
            }


            Question lastQuestion = null;
            for (int i = 0; i < header.Count; i++)
            {
                var item = header[i];

                // Question
                if (item.StartsWith("Q:", StringComparison.OrdinalIgnoreCase) == true)
                {
                    lastQuestion = new Question()
                    {
                        Value = item.TrimStart('q', 'Q').TrimStart(':').TrimStart(' ')
                    };
                    context.Questions.AddObject(lastQuestion);
                }
                else if (item.Length == 0) { }
                // Asnwer
                else
                {
                    if (lastQuestion == null)
                        throw new Exception("Answer found before the first question! "
                            + "Use the Q: or q: perfix to mark questions in the header!");//TODO nem szabad sima Exception-t dobni

                    context.Answers.AddObject(
                        new Answer()
                        {
                            QuestionValue = lastQuestion.Value,
                            Value = item,
                            IsChecked = false
                        });
                    // Effects
                    for (int j = 0; j < allLines.Count; j++)
                    {
                        string qualitySubChar = allValues2[j][0];
                        int weight = 0;
                        int.TryParse(allValues2[j][i + 1], out weight);
                        if (weight > 0)
                        {
                            context.Effects.AddObject(
                                new Effect()
                                {
                                    AnswerQuestionValue = lastQuestion.Value,
                                    AnswerValue = item,
                                    CategoryName = qualitySubChar,
                                    Weight = weight
                                });
                        }
                    }
                }
                context.SaveChanges();
                OnProgressChanged(i, header.Count);
            }

            Debug.Assert(context.Answers.Count() > 0);
            Debug.Assert(context.Effects.Count() > 0);
            OnProgressChanged(100, "...kész.");
        }

        private static void DeleteEffectMatrix(ActualEntities context)
        {
            foreach (var item in context.Effects)
            {
                context.DeleteObject(item);
            }
            context.SaveChanges();
            foreach (var item in context.Answers)
            {
                context.DeleteObject(item);
            }
            context.SaveChanges();
            foreach (var item in context.Questions)
            {
                context.DeleteObject(item);
            }
            context.SaveChanges();
        }

        #endregion Methods

        #region Events

        public event EventHandler<ProgressChangedEventArgs> ProgressChanged;

        protected virtual void OnProgressChanged(ProgressChangedEventArgs e)
        {
            if (this.ProgressChanged != null)
                this.ProgressChanged(this, e);
        }

        protected virtual void OnProgressChanged(int progressPercentage, string userState)
        {
            OnProgressChanged(new ProgressChangedEventArgs(progressPercentage, userState));
        }

        protected virtual void OnProgressChanged(int progress, int maxProgress, object userState = null)
        {
            OnProgressChanged(
                new ProgressChangedEventArgs(
                    (int)((double)progress / (double)maxProgress * 100.0), userState));
        }

        #endregion Events
    }
}
